前言
RePlugin 是今年360技术团队GMTC大会公布的插件化方案。360对插件化的技术探索及优化,在近几年是相当有技术沉淀的。从 DroidPlugin 到 RePlugin, 确实有值得学习的地方。
本文先来简单分析下 RePlugin 的基本原理。
一、引子
普通的: Application extends ContextWrapper, 其中 attachBaseContext() 是从 ContextWrapper 继承过来的,在 Application 的 attach() 中 attachBaseContext() 会被调用:
1 2 3 4
| final void attach(Context context) { attachBaseContext(context); mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; }
|
Application 的 attachBaseContext() 方法 在 attach时被调用。那么 这个 attach() 方法是什么时机被调用呢?在 Instrumentation 类中,找到答案:
1 2 3 4 5 6 7
| static public Application newApplication(Class<?> clazz, Context context) { Application app = (Application)clazz.newInstance(); app.attach(context); return app; }
|
创建 Application 时,会将 Context attach 到 Application 上
另外,后面对于 类加载 知识也是必要的,再做一次复习。
可参考之前写的文章:
https://fenglincanyi.github.io/2016/11/17/Android%20%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%88%9D%E6%8E%A2/
二、Hook时机及Hook点
RePlugin 从 Application 就开始下手了, RePlugin 的 hook 时机:
在 application 的 attachBaseContext 方法中 初始化 Replugin 时(不管是哪种接入方式,都会调用):
1
| RePlugin.App.attachBaseContext(this);
|
在 RePlugin.java 中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public static void attachBaseContext(Application app, RePluginConfig config) { if(sAttached) { if(LogDebug.LOG) { LogDebug.d("RePlugin", "attachBaseContext: Already called"); } } else { RePluginInternal.init(app); RePlugin.sConfig = config; RePlugin.sConfig.initDefaults(app); IPC.init(app); if(LogDebug.LOG && RePlugin.sConfig.isPrintDetailLog()) { LogDebug.printMemoryStatus("RePlugin", "act=, init, flag=, Start, pn=, framework, func=, attachBaseContext, lib=, RePlugin"); } HostConfigHelper.init(); AppVar.sAppContext = app; PluginStatusController.setAppContext(app); PMF.init(app); PMF.callAttach(); sAttached = true; } }
|
PMF.java 中:
1 2 3 4 5 6 7 8 9
| public static final void init(Application application) { setApplicationContext(application); PluginManager.init(application); sPluginMgr = new PmBase(application); sPluginMgr.init(); Factory.sPluginManager = getLocal(); Factory2.sPLProxy = getInternal(); PatchClassLoaderUtils.patch(application); }
|
在 PatchClassLoaderUtils.java 中,进行 hook 操作; 所以, 是在 attachBaseContext() 时 做的 classloader 的 hook。
我们具体来看看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| public static boolean patch(Application application) { try { Context oBase = application.getBaseContext(); if (oBase == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "pclu.p: nf mb. ap cl=" + application.getClass()); } return false; } Object oPackageInfo = ReflectUtils.readField(oBase, "mPackageInfo"); if (oPackageInfo == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "pclu.p: nf mpi. mb cl=" + oBase.getClass()); } return false; } if (LOG) { Log.d(TAG, "patch: mBase cl=" + oBase.getClass() + "; mPackageInfo cl=" + oPackageInfo.getClass()); } ClassLoader oClassLoader = (ClassLoader) ReflectUtils.readField(oPackageInfo, "mClassLoader"); if (oClassLoader == null) { if (LOGR) { LogRelease.e(PLUGIN_TAG, "pclu.p: nf mpi. mb cl=" + oBase.getClass() + "; mpi cl=" + oPackageInfo.getClass()); } return false; } ClassLoader cl = RePlugin.getConfig().getCallbacks().createClassLoader(oClassLoader.getParent(), oClassLoader); ReflectUtils.writeField(oPackageInfo, "mClassLoader", cl); Thread.currentThread().setContextClassLoader(cl); if (LOG) { Log.d(TAG, "patch: patch mClassLoader ok"); } } catch (Throwable e) { e.printStackTrace(); return false; } return true; }
|
这里, 从 baseContext (makeApplication 时创建的 ContextImpl)获取到 mPackageInfo (是 LoadedApk 的实例), 进而 再获取到 mPackageInfo的私有属性 mClassLoader. 拿到 classLoader 了,就可以进行 classLoader 的替换工作了.
三、Hook实现过程
先不直接说怎么做的,我们先看看 这个 mClassLoader 是怎么来的?
在 LoadedApk.Java 中,构造方法里:
1
| mClassLoader = ClassLoader.getSystemClassLoader();
|
继续跟进,
1 2 3
| public static ClassLoader getSystemClassLoader() { return SystemClassLoader.loader; }
|
其中:SystemClassLoader 是 抽象类ClassLoader 的一个静态类:
1 2 3
| static private class SystemClassLoader { public static ClassLoader loader = ClassLoader.createSystemClassLoader(); }
|
此时,就看到了熟悉的 PatchClassLoader:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| private static ClassLoader createSystemClassLoader() { String classPath = System.getProperty("java.class.path", "."); String librarySearchPath = System.getProperty("java.library.path", ""); return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance()); }
|
我们再次回到 上面的 classLoader 替换步骤中, 那么 RepluginClassLoader 就 替换了 PathClassLoader. 后面的 类加载 过程 就依赖于 RePluginClassLoader 来工作了。
其中,oClassLoader 被赋值,就是 原始的 PathClassLoader
下个阶段:RePluginClassLoader 的 loadClass 过程的更改:
首先,原始的ClassLoader 的 loadClass 过程:(路径: Android/sdk/platforms/android-25/android.jar!/java/lang/ClassLoader.class)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null) { long t1 = System.nanoTime(); c = findClass(name); } } return c; }
|
下面是 RePluginClassLoader 的 实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Override protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { Class<?> c = null; c = PMF.loadClass(className, resolve); if (c != null) { return c; } try { c = mOrig.loadClass(className); if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) { LogDebug.d(TAG, "loadClass: load other class, cn=" + className); } return c; } catch (Throwable e) { } return super.loadClass(className, resolve); }
|
之后,PmBase 的 loadClass 过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
| final Class<?> loadClass(String className, boolean resolve) { if (className.startsWith(PluginPitService.class.getName())) { if (LOG) { LogDebug.i(TAG, "loadClass: Loading PitService Class... clz=" + className); } return PluginPitService.class; } if (mContainerActivities.contains(className)) { Class<?> c = mClient.resolveActivityClass(className); if (c != null) { return c; } if (LOGR) { LogRelease.w(PLUGIN_TAG, "p m hlc u d a o " + className); } return DummyActivity.class; } if (mContainerServices.contains(className)) { Class<?> c = loadServiceClass(className); if (c != null) { return c; } if (LOGR) { LogRelease.w(PLUGIN_TAG, "p m hlc u d s o " + className); } return DummyService.class; } if (mContainerProviders.contains(className)) { Class<?> c = loadProviderClass(className); if (c != null) { return c; } if (LOGR) { LogRelease.w(PLUGIN_TAG, "p m hlc u d p o " + className); } return DummyProvider.class; } DynamicClass dc = mDynamicClasses.get(className); if (dc != null) { final Context context = RePluginInternal.getAppContext(); PluginDesc desc = PluginDesc.get(dc.plugin); if (LOG) { LogDebug.d("loadClass", "desc=" + desc); if (desc != null) { LogDebug.d("loadClass", "desc.isLarge()=" + desc.isLarge()); } LogDebug.d("loadClass", "RePlugin.isPluginDexExtracted(" + dc.plugin + ") = " + RePlugin.isPluginDexExtracted(dc.plugin)); } if (desc != null) { String plugin = desc.getPluginName(); if (PluginTable.getPluginInfo(plugin) == null) { if (LOG) { LogDebug.d("loadClass", "plugin=" + plugin + " not found, return DynamicClassProxyActivity.class"); } return DynamicClassProxyActivity.class; } } boolean needStartLoadingActivity = (desc != null && desc.isLarge() && !RePlugin.isPluginDexExtracted(dc.plugin)); if (LOG) { LogDebug.d("loadClass", "needStartLoadingActivity = " + needStartLoadingActivity); } if (needStartLoadingActivity) { Intent intent = new Intent(); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setComponent(new ComponentName(IPC.getPackageName(), "com.qihoo360.loader2.updater.PluginLoadingActivity2")); context.startActivity(intent); } Plugin p = loadAppPlugin(dc.plugin); if (LOG) { LogDebug.d("loadClass", "p=" + p); } if (p != null) { try { Class<?> cls = p.getClassLoader().loadClass(dc.className); if (needStartLoadingActivity) { Tasks.postDelayed2Thread(new Runnable() { @Override public void run() { if (LOG) { LogDebug.d("loadClass", "发广播,让 PluginLoadingActivity2 消失"); } IPC.sendLocalBroadcast2All(context, new Intent("com.qihoo360.replugin.load_large_plugin.dismiss_dlg")); } }, 300); } return cls; } catch (Throwable e) { if (LOGR) { LogRelease.w(PLUGIN_TAG, "p m hlc dc " + className, e); } } } else { if (LOG) { LogDebug.d("loadClass", "加载 " + dc.plugin + " 失败"); } Tasks.postDelayed2Thread(new Runnable() { @Override public void run() { IPC.sendLocalBroadcast2All(context, new Intent("com.qihoo360.replugin.load_large_plugin.dismiss_dlg")); } }, 300); } if (LOGR) { LogRelease.w(PLUGIN_TAG, "p m hlc dc failed: " + className + " t=" + dc.className + " tp=" + dc.classType + " df=" + dc.defClass); } if ("activity".equals(dc.classType)) { return DummyActivity.class; } else if ("service".equals(dc.classType)) { return DummyService.class; } else if ("provider".equals(dc.classType)) { return DummyProvider.class; } return dc.defClass; } return loadDefaultClass(className); }
|
附录
相关资料:
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1223/2205.html
http://www.jianshu.com/p/9c96b68f5ee6
https://github.com/Qihoo360/RePlugin/tree/dev/replugin-host-library
https://fenglincanyi.github.io/2016/11/17/Android%20%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%88%9D%E6%8E%A2/